You are a renowned thief who has recently switched from stealing precious metals to stealing cakes because of the insane profit margins. You end up hitting the jackpot, breaking into the world's largest privately owned stock of cakes—the vault of the Queen of England.
While Queen Elizabeth has a limited number of types of cake, she has an unlimited supply of each type.
Each type of cake has a weight and a value, stored in a tuple with two indices:
For example:
# Weighs 7 kilograms and has a value of 160 shillings
(7, 160)
# Weighs 3 kilograms and has a value of 90 shillings
(3, 90)
You brought a duffel bag that can hold limited weight, and you want to make off with the most valuable haul possible.
Write a function max_duffel_bag_value() that takes a list of cake type tuples and a weight capacity, and returns the maximum monetary value the duffel bag can hold.
For example:
cake_tuples = [(7, 160), (3, 90), (2, 15)]
capacity = 20
# Returns 555 (6 of the middle type of cake and 1 of the last type of cake)
max_duffel_bag_value(cake_tuples, capacity)
Weights and values may be any non-negative integer. Yes, it's weird to think about cakes that weigh nothing or duffel bags that can't hold anything. But we're not just super mastermind criminals—we're also meticulous about keeping our algorithms flexible and comprehensive.
This is a classic computer science puzzle called "the unbounded knapsack problem."
We use a bottom-up approach to find the max value at our duffel bag's weight_capacity by finding the max value at every capacity from 0 to weight_capacity.
We allocate a list max_values_at_capacities where the indices are capacities and each value is the max value at that capacity.
For each capacity, we want to know the max monetary value we can carry. To figure that out, we go through each cake, checking to see if we should take that cake.
The best monetary value we can get if we take a given cake is simply:
To handle weights and values of zero, we return infinity only if a cake weighs nothing and has a positive value.
def max_duffel_bag_value(cake_tuples, weight_capacity):
# We make a list to hold the maximum possible value at every
# duffel bag weight capacity from 0 to weight_capacity
# starting each index with value 0
max_values_at_capacities = [0] * (weight_capacity + 1)
for current_capacity in range(weight_capacity + 1):
# Set a variable to hold the max monetary value so far
# for current_capacity
current_max_value = 0
for cake_weight, cake_value in cake_tuples:
# If a cake weighs 0 and has a positive value the value of
# our duffel bag is infinite!
if cake_weight == 0 and cake_value != 0:
return float('inf')
# If the current cake weighs as much or less than the
# current weight capacity it's possible taking the cake
# would get a better value
if cake_weight <= current_capacity:
# So we check: should we use the cake or not?
# If we use the cake, the most kilograms we can include in
# addition to the cake we're adding is the current capacity
# minus the cake's weight. We find the max value at that
# integer capacity in our list max_values_at_capacities
max_value_using_cake = (
cake_value
+ max_values_at_capacities[current_capacity - cake_weight]
)
# Now we see if it's worth taking the cake. how does the
# value with the cake compare to the current_max_value?
current_max_value = max(max_value_using_cake,
current_max_value)
# Add each capacity's max value to our list so we can use them
# when calculating all the remaining capacities
max_values_at_capacities[current_capacity] = current_max_value
return max_values_at_capacities[weight_capacity]
time, and space, where is number of types of cake and is the capacity of the duffel bag. We loop through each cake ( cakes) for every capacity ( capacities), so our runtime is , and maintaining the list of capacities gives us the space.
Congratulations! Because of dynamic programming, you have successfully stolen the Queen's cakes and made it big.
Keep in mind: in some cases, it might not be worth using our optimal dynamic programming solution. It's a pretty slow algorithm—without any context (not knowing how many cake types we have, what our weight capacity is, or just how they compare) it's easy to see growing out of control quickly if or is large.
If we cared about time, like if there was an alarm in the vault and we had to move quickly, it might be worth using a faster algorithm that gives us a good answer, even if it's not always the optimal answer. Some of our first ideas in the breakdown were to look at cake values or value/weight ratios. Those algorithms would probably be faster, taking time (we'd have to start by sorting the input).
Sometimes an efficient, good answer might be more practical than an inefficient, optimal answer.
This question is our spin on the famous "unbounded knapsack problem"—a classic dynamic programming question.
If you're struggling with dynamic programming, we have reference pages for the two main dynamic programming strategies: memoization and going bottom-up.
Do you have an answer?
Wanna review this one again later? Or do you feel like you got it all?
Mark as done Pin for review laterReset editor
Powered by qualified.io